【Kotlin】一文吃透 Kotlin 中眼花缭乱的函数家族

您所在的位置:网站首页 length in char 属性不匹配 【Kotlin】一文吃透 Kotlin 中眼花缭乱的函数家族

【Kotlin】一文吃透 Kotlin 中眼花缭乱的函数家族

2023-04-10 05:19| 来源: 网络整理| 查看: 265

一、扩展函数

Kotlin 可以实现扩展一个类的新功能而无需继承该类。比如可以为一个不能修改的第三方库中的类编写一个新的函数,这个新增的函数就像那个原始类本来就有的函数一样,可以用普通的方法调用。这种机制称为扩展函数。

来看一个典型的扩展函数写法:

fun String.lastChar(): Char = this[length - 1] String. 表示扩展的目标类; lastChar 即函数名; Char 即函数返回类型; this 代表当前类的实例,并非必须、可省略; [...] 即函数体。 二、在 Kotlin 和 Java 中不同的调用方法

Kotlin 中直接调用:

class Test { ... fun main() { ... println("last char: ${"Tyhoo".lastChar()}") } } fun String.lastChar(): Char = this[length - 1]

Java 中则是像静态类一样调用该扩展方法,要注意两点:

Java 中当静态方法调用它,类名为扩展函数所存在的 Kt 文件名 + Kt。此处即为 TestKt; 调用函数传入的第一个参数为实例,其后为函数参数。 public class Test { static void main(String[] args) { System.out.println("last char: " + TestKt.lastChar("Tyhoo")); } } 三、泛型扩展函数

对于泛型类的也可以拥有扩展函数,只不过需要在声明的函数前指定泛型,否则无法扩展成功。

比如给 MutableMap 添加新的函数:

fun MutableMap.putLast(): V? { ... } 四、与 apply 等函数的关系

以 apply 函数,了解下源码中扩展函数的应用:

@kotlin.internal.InlineOnly public inline fun T.apply(block: T.() -> Unit): T { contract { callsInPlace(block, InvocationKind.EXACTLY_ONCE) } block() return this }

apply 和 let 等函数一样是扩展自任意类型对象的函数,因为泛型的缘故在函数前添加了 的声明、block 参数的 T.()->Unit 指的是带有指向 T 实例的 this 的参数并无返回值。

T.()->Unit 这种带有接收者的参数形式被称为函数字面值,其和扩展函数的形式有点像,但并不是。通过反编译之后会发现它仍然属于匿名函数的范畴,通过 Function2 接口实现,只不过传入的参数是 T 其本身。

apply 函数返回 T 类型,内部则是调用 block() 传入 T 对象,进行处理之后,返回对象本身。

从如下的 run 函数的源码可以看出与 apply 之间的区别: 其函数参数和返回值均是 R 类型,这将导致像 let 和 also 一样的不同点:

apply 总是返回的是对象本身; run 返回的是函数结果。 @kotlin.internal.InlineOnly public inline fun T.run(block: T.() -> R): R { contract { callsInPlace(block, InvocationKind.EXACTLY_ONCE) } return block() } 五、扩展属性

扩展属性提供了一种方法能通过属性语法进行访问的 API 来扩展。尽管它们被叫做属性,但是它们不能拥有任何状态,它不能添加额外的字段到现有的 Java 对象实例。

比如下面的为 List 添加一个 last 属性用于获取列表的最后一个元素,this 可以省略。注意: 泛型仍要声明在扩展属性前。

val List.last: T get() = get(size - 1) val listString = listOf("Android T", "Android S", "Android R") fun main() { println("listString.last: ${listString.last}") } 六、与 KTX 的关系

KTX 是专门为 Android 库设计的 Kotlin 扩展程序,以提供简洁易用的 Kotlin 代码,其中部分 KTX 采用了扩展属性的写法,比如 viewModelScope。

它向 ViewModel 类扩展了 viewModelScope 属性,供 ViewModel 中便捷地使用 CoroutineScope: 绑定至 Dispatchers.Main,并且会在清除 ViewModel 后自动取消。

private const val JOB_KEY = "androidx.lifecycle.ViewModelCoroutineScope.JOB_KEY" public val ViewModel.viewModelScope: CoroutineScope get() { val scope: CoroutineScope? = this.getTag(JOB_KEY) if (scope != null) { return scope } return setTagIfAbsent( JOB_KEY, CloseableCoroutineScope(SupervisorJob() + Dispatchers.Main.immediate) ) } 七、伴生对象扩展函数和属性

如果一个类定义了伴生对象,那么我们也可以为伴生对象定义扩展函数与属性,并且就可以和伴生对象一样使用类名直接访问:

class Job { companion object {} } class Test { fun main() { Job.print("Extension for Companion object.") } } fun Job.Companion.print(summary: String) { println("Job: $summary") } 八、原理

看下上述 String.lastChar() 扩展函数反编译后的代码:

即在 TestKt Class 内生成了同名的静态函数,接收的参数即为目标 Class 即 String 实例,内部将调用扩展函数的函数体。

public final class TestKt { public static final char lastChar(@NotNull String $this$lastChar) { Intrinsics.checkNotNullParameter($this$lastChar, "$this$lastChar"); return $this$lastChar.charAt($this$lastChar.length() - 1); } }

再看下 viewModelScope KTX 的反编译来研究下扩展属性的原理:

同样在 XXXKt 的 ViewModelKt Class 内生成了静态方法,不过名称为 getXXX 形式,其接收的参数为 ViewModel 实例,内部执行 get() 的逻辑并返回。

public final class ViewModelKt { private static final String JOB_KEY = "androidx.lifecycle.ViewModelCoroutineScope.JOB_KEY"; @NotNull public static final CoroutineScope getViewModelScope(@NotNull ViewModel $this$viewModelScope) { Intrinsics.checkNotNullParameter($this$viewModelScope, "$this$viewModelScope"); CoroutineScope scope = (CoroutineScope)$this$viewModelScope.getTag("androidx.lifecycle.ViewModelCoroutineScope.JOB_KEY"); if (scope != null) { return scope; } else { Object var10000 = $this$viewModelScope.setTagIfAbsent("androidx.lifecycle.ViewModelCoroutineScope.JOB_KEY", new CloseableCoroutineScope(SupervisorKt.SupervisorJob$default((Job)null, 1, (Object)null).plus((CoroutineContext)Dispatchers.getMain().getImmediate()))); Intrinsics.checkNotNullExpressionValue(var10000, "setTagIfAbsent(\n …Main.immediate)\n )"); return (CoroutineScope)var10000; } } } 九、不要滥用

扩展函数、扩展属性虽好但不要滥用,因为会造成一些弊端:

扩展函数无法像普通函数那样进行函数引用; 多个接受者隐式的访问可能会令人困惑; 当修改引用接受者的时候,不清楚修改的是扩展接受者还是调度接受者; 对于经验较少的开发人员来说,看到成员扩展可能是违反直觉,可读性很差。


【本文地址】


今日新闻


推荐新闻


CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3